/*
 * (C) Copyright 2011
 *
 * Yuri Tikhonov, Emcraft Systems, yur@emcraft.com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

/*
 * STM32 USART driver; configured with the following options:
 * - CONFIG_STM32_USART_CONSOLE
 * - CONFIG_STM32_USART_PORT       (1..6)
 * - CONFIG_STM32_USART_TX_IO_PORT (0..8 <-> A..I)
 * - CONFIG_STM32_USART_RX_IO_PORT (0..8 <-> A..I)
 * - CONFIG_STM32_USART_TX_IO_PIN  (0..15)
 * - CONFIG_STM32_USART_RX_IO_PIN  (0..15)
 */

#include <common.h>
#include "asm/global_data.h"


/*
 * Initialize the serial port.
 */
s32 serial_init(void)
{
    u32 temp;
	/*UART clk is from OSC24Mhz clock*/
	temp = CCM_CSCDR1;
	temp |= CCM_CSCDR1_UART_CLK_SEL_MASK;	//uart clk from OSC24Mhz
	temp &= ~CCM_CSCDR1_UART_CLK_PODF_MASK;     // divide by 1
	CCM_CSCDR1 = temp ;
	
	u32 lpuart_clk_khz = 24000000;
	
	/*LPUART1_RX, GPIO_AD_B0_13, ALT2*/
	IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_13 = IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_13_MUX_MODE(2); 
	/*LPUART1_TX, GPIO_AD_B0_12, ALT2*/
	IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_12 = IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_13_MUX_MODE(2); 
	
	/*lpuart init baudrate*/
	serial_setbrg();
	return 0;
}

/*
 * Set new baudrate.
 */
void serial_setbrg(void)
{
	u8 i;
    u32 calculated_baud = 0;
    u32 baud_diff = 0;
    u32 osr_val = 0;
    u32 sbr_val, lpuartclk;
    u32 baud_rate;
    u32 reg_temp = 0;
    u32 temp = 0;
	u32 sysclk = 24000;
	// TODO: baudrate config
	//u32 baud = gd->baudrate;
	u32 baud = 115200;
	LPUART_MemMapPtr uartch = LPUART1_BASE_PTR;
	// Disable LPUART before changing registers
    LPUART_CTRL_REG(uartch) &= ~(LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK); 
	// Initialize baud rate
    baud_rate = baud;
    // Change units to Hz
    lpuartclk = sysclk * 1000;
    // Calculate the first baud rate using the lowest OSR value possible.  
    i = 4;
    sbr_val = (u32)(lpuartclk/(baud_rate * i));
    calculated_baud = (lpuartclk / (i * sbr_val));
    if (calculated_baud > baud_rate)
        baud_diff = calculated_baud - baud_rate;
    else
        baud_diff = baud_rate - calculated_baud;
    osr_val = i;
    
    // Select the best OSR value
    for (i = 5; i <= 32; i++)
    {
        sbr_val = (u32)(lpuartclk/(baud_rate * i));
        calculated_baud = (lpuartclk / (i * sbr_val));
        if (calculated_baud > baud_rate)
            temp = calculated_baud - baud_rate;
        else
            temp = baud_rate - calculated_baud;
        if (temp <= baud_diff)
        {
            baud_diff = temp;
            osr_val = i; 
        }
    }
    
    if (baud_diff < ((baud_rate / 100) * 3))
    {
        // If the OSR is between 4x and 8x then both
        // edge sampling MUST be turned on.  
        if ((osr_val >3) && (osr_val < 9))
            LPUART_BAUD_REG(uartch) |= LPUART_BAUD_BOTHEDGE_MASK; 
        // Setup OSR value 
        reg_temp = LPUART_BAUD_REG(uartch);
        reg_temp &= ~LPUART_BAUD_OSR_MASK;
        reg_temp |= LPUART_BAUD_OSR(osr_val-1);
        // Write reg_temp to C4 register
        LPUART_BAUD_REG(uartch) = reg_temp;
        reg_temp = ((reg_temp & LPUART_BAUD_OSR_MASK)>>24) + 1;
        sbr_val = (u32)((lpuartclk)/(baud_rate * (reg_temp)));
        /* Save off the current value of the uartx_BDH except for the SBR field */
        reg_temp = LPUART_BAUD_REG(uartch) & (~(LPUART_BAUD_SBR(0x1FFF)));
        LPUART_BAUD_REG(uartch) = reg_temp|LPUART_BAUD_SBR((sbr_val & 0x1FFF));
               
        /* Enable receiver and transmitter */
        LPUART_CTRL_REG(uartch) |= (LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK);
           
    }
    else
    {
        // Unacceptable baud rate difference
        // More than 3% difference!!
        // Enter infinite loop!
        while(1)
        {}
    }                   

	return;
}

/*
 * Read a single character from the serial port.
 */
s32 serial_getc(void)
{
	/* Wait until character has been received */
    while (!(LPUART_STAT_REG(LPUART1_BASE_PTR) & LPUART_STAT_RDRF_MASK));
    /* Return the 8-bit data from the receiver */
    return (s32)(LPUART_DATA_REG(LPUART1_BASE_PTR)&0xFF);
}

/*
 * Put a single character to the serial port.
 */
void serial_putc(const char c)
{
	u32 temp;
	if (c == '\n')
		serial_putc('\r');
    /* Wait until space is available in the FIFO */
    while (!(LPUART_STAT_REG(LPUART1_BASE_PTR) & LPUART_STAT_TDRE_MASK));
    temp = LPUART_DATA_REG(LPUART1_BASE_PTR)&0xFFFFFF00;
    /* Send the character */
    LPUART_DATA_REG(LPUART1_BASE_PTR) = (u32)(temp|(u32)c);
}

/*
 * Put a string ('\0'-terminated) to the serial port.
 */
void serial_puts(const char *s)
{
	while (*s)
		serial_putc(*s++);
}

/*
 * Test whether a character in in the RX buffer.
 */
s32 serial_tstc(void)
{
#if defined (CONFIG_SYS_STM32F7)
	if (LPUART_STAT_REG(LPUART1_BASE_PTR) & LPUART_STAT_RDRF_MASK){
		return 0;
	}else{
		return 1;
	}
#endif
}
